package ferp.android.managers;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.preference.PreferenceManager;
import ferp.android.BuildConfig;
import ferp.android.tasks.center.TaskGameReportSend;
import ferp.core.Version;
import ferp.core.game.Game;
import ferp.core.game.Settings;
import ferp.core.game.Statistics;
import ferp.core.log.Log;
import ferp.core.player.Profile;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.*;

/**
 * User: igorgok
 * Date: 9/12/12 12:03 PM
 */
public abstract class ProfileManager implements Comparator<Profile>
{
  public static final class Regular extends ProfileManager
  {
    @Override
    public int compare(Profile p1, Profile p2)
    {
      return p1.nickname.compareTo(p2.nickname);
    }

    @Override
    protected File getFolder() {return Settings.Folders.instance(context.getFilesDir()).profiles;}

    @Override
    protected String getLastProfileIdKey() {return "lastProfileID";}

    private Regular(Context context) {super(context);}
  }

  public static final class Tutorial extends ProfileManager
  {
    @Override
    public int compare(Profile p1, Profile p2)
    {
      return Integer.valueOf(p1.id).compareTo(Integer.valueOf(p2.id));
    }

    public void setCurrentProfileByID(String id)
    {
      int index = getProfileIndex(id);

      if (index != -1)
        setCurrentProfile(index);
    }

    public Profile newCurrentProfile(String id) throws Exception
    {
      Profile tutorial = newCurrentProfile();

      tutorial.id = id;
      tutorial.nickname = tutorial.id;
      tutorial.useCenter = false;

      tutorial.settings.roundEndTapWhenNotPlaying = false;
      tutorial.settings.roundEndTapWhenLastMoverAndroid = false;

      tutorial.settings.confirmations.bid = Settings.Confirmation.NEVER;
      tutorial.settings.confirmations.contract = Settings.Confirmation.NEVER;

      saveCurrentProfile(false);

      return tutorial;
    }

    public Settings getDefaultSettings(Profile regular)
    {
      Settings settings = new Settings();

      if (regular == null)
      {
        settings.locale = Settings.Locale.locale(Locale.getDefault().getLanguage());
        settings.orientation = getCurrentOrientation();
        settings.cardMovingMode = Settings.CardMovingMode.DRAG;
        settings.animationTime = Settings.ANIMATION_TIME;
      }
      else
      {
        settings.locale = regular.settings.locale;
        settings.orientation = regular.settings.orientation;
        settings.cardMovingMode = regular.settings.cardMovingMode;
        settings.animationTime = regular.settings.animationTime;
      }

      return settings;
    }

    @Override
    protected File getFolder() {return Settings.Folders.instance(context.getFilesDir()).tutorial;}

    @Override
    protected String getLastProfileIdKey() {return "lastTutorialProfileID";}

    private Tutorial(Context context) {super(context);}
  }

  // an arbitrary context can be passed here as we will get the application context and use it!
  public static ProfileManager.Regular regular(Context context)
  {
    if (regular == null)
      regular = new Regular(context);

    return regular;
  }

  public static ProfileManager.Tutorial tutorial(Context context)
  {
    if (tutorial == null)
      tutorial = new Tutorial(context);

    return tutorial;
  }

  public Profile getCurrentProfile()
  {
    return selected == null ? setCurrentProfile() : selected;
  }

  public Version version() {return version;}

  public Profile setCurrentProfile()
  {
    // set the current profile if any
    int index = getProfileIndex(getLastProfileID());

    if (index == -1)
    {
      // profile not found, set to first available
      if (profiles.size() > 0)
        setCurrentProfile(0);
      else
        selected = null;
    }
    else
      selected = profiles.get(index);

    return selected;
  }

  public Profile setCurrentProfile(int index)
  {
    selected = profiles.get(index);

    setLastProfileID(selected.id);

    return selected;
  }

  public Profile reloadCurrentProfile() throws Exception
  {
    Game g = selected.game();
    // it is safe to keep reference to game's log since game object will be recreated when profile is loaded
    List<String> log = g == null ? null : g.log;

    selected = Profile.load(selected.getFileObject(false));
    profiles.set(getProfileIndex(selected.id), selected);

    g = selected.game();

    if (g != null && log != null)
      g.log = log;

    return selected;
  }

  public int getCurrentProfileIndex() {return profiles.indexOf(selected);}

  public Profile newCurrentProfile()
  {
    // create new profile
    Profile p = new Profile(getFolder());

    p.settings.locale = (selected == null ?  Settings.Locale.locale(Locale.getDefault().getLanguage()) : selected.settings.locale);
    p.settings.orientation = (selected == null ? getCurrentOrientation() : selected.settings.orientation);

    return (selected = p);
  }

  public Profile find(String id)
  {
    for (Profile p : profiles)
      if (p.id.equals(id))
        return p;

    return null;
  }

  public List<Profile> getProfileList() {return profiles;}

  public int getProfileIndex(String id)
  {
    for (int i = 0; i < profiles.size(); ++i)
      if (profiles.get(i).id.equals(id))
        return i;

    return -1;
  }

  public void saveCurrentProfile(boolean deleteGame) throws Exception
  {
    if (deleteGame)
    {
      Statistics total = selected.getStatistics();
      Game g = selected.game();

      // update statistics
      if (g != null && g.statistics.sets.deals > 0)
      {
        total.update(g.statistics);
        total.games.deleted++;
      }

      selected.clearGame();
    }

    // save to file
    selected.save();
    // save last ID
    setLastProfileID(selected.id);

    // if it's new profile add it to the list
    if (profiles.indexOf(selected) == -1)
    {
      profiles.add(selected);
      // sort by nick name
      Collections.sort(profiles, this);
    }
  }

  public void deleteCurrentProfile(boolean deleteFromCloud)
  {
    deleteProfile(selected, deleteFromCloud);
  }

  public void deleteProfile(String nickname)
  {
    for (int i = 0; i < profiles.size(); ++i)
    {
      Profile p = profiles.get(i);

      if (p.nickname.equals(nickname))
      {
        setCurrentProfile(i);
        deleteCurrentProfile(false);

        return;
      }
    }
  }

  public void deleteProfile(Profile p, boolean deleteFromCloud)
  {
    // delete profile on cloud
    if (deleteFromCloud && p.settings.cloudSave != Settings.CloudSave.NONE)
      CloudManager.instance(context).delete(p.id);

    // delete local profile
    int index = profiles.indexOf(p);

    if (index != -1)
    {
      profiles.remove(index);
      p.delete();

      // set new current profile
      if (profiles.size() > 0)
      {
        if (selected == null || p == selected)
          setCurrentProfile(Math.min(index, profiles.size() - 1));
        else
          setCurrentProfile(profiles.indexOf(selected));
      }
      else
      {
        selected = null;
        removeLastProfileID();
      }
    }
  }

  public void addProfile(Profile p)
  {
    try
    {
      // once we have an old version of the loaded profile - remove it from the list
      // the contents will be replaced with the new one
      Profile existing = find(p.id);

      if (existing != null)
        profiles.remove(existing);

      // set the appropriate folder
      p.folder = getFolder();

      p.save();
      profiles.add(p);
    }
    catch (IOException e)
    {
      Log.error(Log.TAG, e);
    }
  }

  protected abstract File getFolder();
  protected abstract String getLastProfileIdKey();

  // application context
  protected final Context context;

  protected Settings.Orientation getCurrentOrientation()
  {
    return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? Settings.Orientation.LANDSCAPE : Settings.Orientation.PORTRAIT;
  }

  // regular and tutorial profile manager instances
  private static ProfileManager.Regular regular = null;
  private static ProfileManager.Tutorial tutorial = null;

  // list of loaded profiles
  private final ArrayList<Profile> profiles = new ArrayList<Profile>();
  // currently selected profile
  private Profile selected = null;
  // package info
  private final Version version = new Version();
  // buffer for error messages
  private final StringBuilder errors = new StringBuilder(256);

  private ProfileManager(Context context)
  {
    // ensure we use application context here
    this.context = context.getApplicationContext();

    setupVersion();
    loadProfiles();
  }

  private void setupVersion()
  {
    // initialize app version data
    try
    {
      // get app version
      PackageInfo pi = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);

      version.name = pi.versionName;
      version.code = pi.versionCode;
    }
    catch (PackageManager.NameNotFoundException e)
    {
      version.name = "n/a";
      version.code = 0;
    }
  }

  private void loadProfiles()
  {
    try
    {
      // verify data folders
      Settings.Folders.instance(context.getFilesDir()).verify();

      // get rid of stored profiles
      profiles.clear();

      // get file list (includes all file except the temporary one)
      File[] files = getFolder().listFiles(new FilenameFilter()
      {
        @Override
        public boolean accept(File dir, String name)
        {
          return !name.equals(Profile.TEMPORARY);
        }
      });

      if (files != null)
      {
        // build list of profiles which includes real and backup profile data files
        HashMap<String, LinkedList<File>> map = new HashMap<String, LinkedList<File>>();

        Log.debug(Log.TAG, "processing profiles, total: " + files.length);

        for (File f : files)
        {
          String name = f.getName();
          // determine real id dropping the backup extension if any
          boolean backup = name.endsWith(Profile.BACKUP_EXTENSION);
          String id = backup ? name.substring(0, name.lastIndexOf(".")) : name;
          // associate both current and backup profile with the id
          LinkedList<File> list = map.get(id);

          Log.debug(Log.TAG, "profile [" + id + "] - " + (backup ? "backup" : "real"));

          if (list == null)
            map.put(id, list = new LinkedList<File>());

          // backup file must come after the real one
          if (backup)
            list.addLast(f);
          else
            list.addFirst(f);
        }

        // load profiles
        for (LinkedList<File> list : map.values())
          load(list);

        // sort by nick name
        Collections.sort(profiles, this);
      }
      else
        Log.debug(Log.TAG, "file list is null");
    }
    catch (Exception e)
    {
      error(e.getMessage());
    }
  }

  private void load(LinkedList<File> list)
  {
    // create error buffer only if required
    errors.setLength(0);

    for (File f : list)
      try
      {
        // load individual profile
        Profile p = Profile.load(f);

        if (p != null && p.nickname != null)
        {
          profiles.add(p);

          return;
        }

        boolean b = BuildConfig.DEBUG || f.delete();

        errors.append("file=").append(f.getName()).append(", size=").append(f.length()).append(", profile=").append(p).append(", nickname=").append(p == null ? null : p.nickname).append("\ndeleted=").append(b).append("\n");
      }
      catch (Exception e)
      {
        Log.error(Log.TAG, e);
        // delete corrupted file
        boolean b = BuildConfig.DEBUG || f.delete();

        errors.append("file=").append(f.getName()).append(", size=").append(f.length()).append(", exception: ").append(e.getClass().getName()).append(", message: ").append(e.getMessage()).append("\ndeleted=").append(b).append("\n");
      }

    error(errors.length() > 0 ? errors.toString() : "no files in list for profile");
  }

  private void error(String description) {new TaskGameReportSend(null, version, "load@profiles", description, null, null).execute();}

  private void setLastProfileID(String id)
  {
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
    SharedPreferences.Editor editor = preferences.edit();

    editor.putString(getLastProfileIdKey(), id);
    editor.commit();
  }

  private String getLastProfileID() {return PreferenceManager.getDefaultSharedPreferences(context).getString(getLastProfileIdKey(), "");}

  private void removeLastProfileID()
  {
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
    SharedPreferences.Editor editor = preferences.edit();

    editor.remove(getLastProfileIdKey());
    editor.commit();
  }
}
